#pragma once
#include<iostream>
#include<sstream>
#include<fstream>
#include<QMutex>
#include<QDir>
#include<QString>
#include"MemUtil.h"
using namespace std;

class IOUtil{

private:

    static string totalPath;

    static int nameMaxLen;

    static string recordFileName;

    static int64_t recordFileMaxLen;

    //系统调用虽然原子性的，但是这里对文件的操作都是多个系统调用组成的。所以在内核态加锁
    //其他的文件在设计上就可以避免并发
    static QMutex recordFileMutex;

public:

    static string getRecordPath(){
        return totalPath + "\\" + recordFileName + ".bin";
    }

    static void setRecordFileName(const string& name){
        recordFileName = name;
    }

    static int getNameMaxLen(){
        return nameMaxLen;
    }

    static void setNameMaxLen(const int& nml){
        nameMaxLen = nml;
    }

    static string getTotalPath(){
        return totalPath;
    }

    static void setTotalPath(const string& path){
        totalPath = path;
    }

    static void setRecordFileMaxLen(const int64_t& maxLen){
        recordFileMaxLen = maxLen;
    }

    static fstream getGoodFstream(const string& path, const ios_base::openmode& mode){
        fstream f(path, mode);
        if(!f.good()){
            throw string("file error");
        }
        return f;
    }

    static fstream getFstreamCreateIfNotExist(const string& path){
        try {
            return IOUtil::getGoodFstream(path, ios::in | ios::out | ios::binary); //尝试获取文件，没有文件会抛异常。
        }  catch (string) {//在这获取异常，创建文件。
            return IOUtil::getGoodFstream(path, ios::out | ios::binary);   //返回只可以写入的文件描述符
        }
    }


    static bool initFstreamIfExist(fstream& f, const string& path){
        bool isExist = false;
        try {
            f = IOUtil::getGoodFstream(path, ios::in | ios::out | ios::binary); //尝试获取文件，没有文件会抛异常。
            isExist = true;    //成功初始化会置true
        }  catch (string) {}
        return isExist;
    }

    static void createDirectory(const string& path){
        const QString& qStrPath = QString::fromStdString(path);
        QDir dir(qStrPath);
        if(!dir.exists() && !dir.mkpath(qStrPath)){
            throw string("Directory creation failed");
        }
    }

    static void removeFile(const string& path){
        if(remove(path.c_str()) != 0){
            throw string("fail to remove file");
        }
    }

    //获取文件长度
    static int64_t getFileLen(fstream& f){
        f.seekg(0, ios_base::end);
        return f.tellg();
    }

    //整体覆盖写。
    static void byteWrite(char* buffer, const string& path, const int64_t& size){
        fstream f = getGoodFstream(path, ios::out | ios::trunc | ios::binary);
        f.seekp(0, ios::beg);
        f.write(buffer, size);
        f.sync();
        f.close();                //此函数不负责缓冲区释放
    }

    static void byteRead(char* buffer, fstream& f, const int64_t& size){
        f.seekg(0, ios::beg);
        f.read(buffer, size);
    }


    static void charWrite(const string& str, const string& path){
        fstream f = getGoodFstream(path, ios::out | ios::trunc);
        f.seekp(0, ios::beg);
        f << str;
        f.sync();
        f.close();
    }

    static string charRead(const string& path){
        fstream f = getGoodFstream(path, ios::in);
        stringstream ss;
        f.seekg(0, ios::beg);
        ss << f.rdbuf();
        f.close();
        return ss.str();
    }


    //创建并返回记录文件
    static fstream createRecordFile(){
        fstream f = getGoodFstream(IOUtil::getRecordPath(), ios::out | ios::binary);
        int64_t endPos = 2 * sizeof (int64_t);
        f.seekp(0, ios::beg);
        //指向可以继续写入记录的位置(有效位置)（用来保证原子性）
        f.write((char*)&endPos, sizeof (int64_t));
        //指向未更新到数据库的第一条记录头部（考虑到效率 批量延迟更新）
        f.write((char*)&endPos, sizeof (int64_t));
        f.sync();
        return f;
    }

    //创建并返回skipList 文件描述符
    //文件开头三个数据
    //第一个是 数据版本号 用于原子性文件重写
    //第二个是 有效数据长度
    //第三个是 LSMtree 有序列表数量（用于LSMtree合并）
    static fstream createSLIndexFile(const string& path, const int64_t& dataVersion){
        fstream f = getGoodFstream(path, ios::out | ios::binary);
        int64_t endPos = 3 * sizeof (int64_t), count = 0;
        f.seekp(0 , ios::beg);
        f.write((char*)&dataVersion, sizeof (int64_t));
        f.write((char*)&endPos, sizeof (int64_t));
        f.write((char*)&count, sizeof (int64_t));
        f.sync();
        return f;
    }


    //在记录文件追加写记录
    //如果没有此文件就创建
    //批量写入一整个事务中的记录，当写入成功后更新记录的有效位置。以此来保证事务acid。
    static void appRecordFromEnd(char* byteData, const int64_t& dataLen){
        fstream f;
        int64_t infoEndPos;
        recordFileMutex.lock();
        try {
            f = getGoodFstream(IOUtil::getRecordPath(), ios::in | ios::out | ios::binary);
            infoEndPos = readPairMetaInfo(f).first;    //读取元数据
        }  catch (string) {
            f = createRecordFile();                //没有记录文件就创建
            infoEndPos = sizeof (int64_t) * 2;
        }
        //从有效记录末尾位置开始写新记录
        f.seekp(infoEndPos, ios::beg);
        f.write(byteData, dataLen);
        f.sync();
        //获取最新的有效记录末尾位置，并更新在0字节处。
        //原子写记录
        infoEndPos += dataLen;
        f.seekp(0, ios::beg);
        f.write((char*)&infoEndPos, sizeof (int64_t));
        f.sync();
        f.close();
        recordFileMutex.unlock();
    }

    //往索引文件追加写 （lsmTree） 更新头几个字节数据
    static void appIndexRecordFromEnd(fstream& f, const int64_t& writeStartLoc, const int64_t& count, const char* byteData, const int64_t& dataLen){
        int64_t newEffectLoc = writeStartLoc + sizeof (int64_t) + dataLen, newSortedIndRecVecCount = count + 1;
        f.seekp(writeStartLoc, ios::beg);   //将写指针定位到最后有效位置
        f.write((char*)&dataLen, sizeof(int64_t));  //先写数据长度
        f.write(byteData, dataLen);     //再写数据
        f.sync();
        f.seekp(sizeof(int64_t), ios::beg);
        f.write((char*)&newEffectLoc, sizeof(int64_t));
        f.write((char*)&newSortedIndRecVecCount, sizeof (int64_t));
        f.sync();
    }

    static void updateSyncProgress(const int64_t& moveByteCount){
        if(moveByteCount == 0){ return; }
        const string& recordFilePath = IOUtil::getRecordPath();
        fstream f = getGoodFstream(recordFilePath, ios::in | ios::out | ios::binary);
        //获取未更新到数据库的第一条记录位置
        recordFileMutex.lock();
        //日志文件大小超过阈值 且 日志文件全部都已经同步到数据文件时 可以删除日志文件（后续使用重新创建）
        const pair<int64_t, int64_t>& metaInfo = readPairMetaInfo(f);
        if(metaInfo.first >= recordFileMaxLen &&
           metaInfo.first == metaInfo.second + moveByteCount){
            f.close();  //删除前需要关闭文件描述符
            removeFile(recordFilePath);
        }
        else{
            int64_t infoEndPos;
            f.seekg(sizeof(int64_t), ios::beg);
            f.read((char*)&infoEndPos, sizeof (int64_t));
            //更新位置
            infoEndPos += moveByteCount;
            f.seekp(sizeof(int64_t), ios::beg);
            f.write((char*)&infoEndPos, sizeof (int64_t));
            f.sync();
            f.close();
        }
        recordFileMutex.unlock();
    }

    //读取有两个int64的元数据
    static pair<int64_t, int64_t> readPairMetaInfo(fstream& f){
        int64_t first, second;
        f.seekg(0, ios::beg);
        f.read((char*)&first, sizeof (int64_t));
        f.read((char*)&second, sizeof (int64_t));
        return make_pair(first, second);
    }

    static tuple<int64_t, int64_t, int64_t> readTupleMetaInfo(fstream& f){
        int64_t info0, info1, info2;
        f.seekg(0, ios::beg);
        f.read((char*)&info0, sizeof (int64_t));
        f.read((char*)&info1, sizeof (int64_t));
        f.read((char*)&info2, sizeof (int64_t));
        return make_tuple(info0, info1, info2);
    }

    static void writeSLIndexFileAndSwitch(fstream& f, const char* byteData, const int64_t& dataLen, const int64_t& dataVersion){
        int64_t effectLoc = dataLen + (sizeof (int64_t) * 4), count = 1;
        f.seekp(sizeof (int64_t) * 3 ,ios::beg);
        //先写数据长度
        f.write((char*)&dataLen, sizeof (int64_t));
        //再写数据
        f.write(byteData, dataLen);
        f.sync();
        //更新元数据
        f.seekp(sizeof (int64_t) ,ios::beg);
        f.write((char*)&effectLoc, sizeof (int64_t));
        f.write((char*)&count, sizeof (int64_t));
        f.sync();
        //原子性的写版本号 使替换操作具有原子性
        f.seekp(0 ,ios::beg);
        f.write((char*)&dataVersion, sizeof (int64_t));
        f.sync();
    }

    //数据重做时判断数据是否已写入但是没更新写入进度
    //通过判断文件末尾数据和要写入的数据是否一致来判断
    static bool isIndexRecordBeenWritten(fstream& f, const int64_t& writeStartLoc, const char* byteData, const int64_t& dataLen){
        const int64_t& checkReadStart = writeStartLoc - dataLen;
        if(checkReadStart < 0){
            return false;
        }
        char* checkData = new char[dataLen];
        f.seekg(checkReadStart, ios::beg);
        f.read(checkData, dataLen);     //==0时相同 -》 已被写入
        const bool& isBeenWritten = memcmp(checkData, byteData, dataLen) == 0;
        MemUtil::asyncClearPtrArrMem(checkData);
        return isBeenWritten;
    }

};
